/*
 * simplemq_commons.hpp
 *
 *  Created on: 2021年10月11日
 *      Author: guyadong
 */

#ifndef SIMPLEMQ_SIMPLEMQ_COMMONS_HPP_
#define SIMPLEMQ_SIMPLEMQ_COMMONS_HPP_
#include <sstream>
#include <limits>
#include <memory>
#include <algorithm>
#include <uri/uri.hh>
#include <json_utilits.hpp>

namespace simplemq {
	static const std::string DEFAULT_HOST{ "127.0.0.1" };
	static const int DEFAULT_MQTT_PORT = 1883;
	static const int DEFAULT_REDIS_PORT = 6379;
	static const std::string MQTT_SCHEMA = "tcp";
	static const std::string REDIS_SCHEMA = "redis";
	static const std::string DEFAULT_USERNAME{ "" };
	static const std::string DEFAULT_PASSWORD{ "" };
	static const int DEFAULT_TIMEOUT_MS = 2000;
	static const int DEFAULT_MAX_RECONNECTS = -1;
	static const int DEFAULT_RECONNECT_INTERVAL_MS = 2000;
	static const std::string MQTT_CLIENT_ID_PREFIX{ "paho_mqtt_client" };
	/************************************************************************/
	/* json 格式参数字段名定义                                              */
	/************************************************************************/
	static const std::string JN_HOST = "host";
	static const std::string JN_PORT = "port";
	static const std::string JN_USERNAME = "username";
	static const std::string JN_PASSWORD = "password";
	static const std::string JN_TIMEOUT_MS = "timeout_ms";
	static const std::string JN_MAX_RECONNECTS = "max_reconnects";
	static const std::string JN_RECONNECT_INTERVAL_MS = "reconnect_interval_ms";
	inline int int_value(const std::map<std::string, std::string> &m, const std::string &key, int def) {
		try
		{
			auto i = std::atoi(m.at(key).c_str());
			// 为0则使用默认值
			if (i)return i;
		}
		catch (const std::out_of_range&) {
			// NOT FOUND,DO NOTHING
		}
		return def;
	}
	inline int int_value(const uri& uri, const std::string &key, int def) {
		auto query = uri.get_query_dictionary();
		return int_value(query, key, def);
	}
	inline nlohmann::ordered_json parse_components(const uri& uri)
	{
		nlohmann::ordered_json j;
		j["schema"] = uri.get_scheme();
		j["host"] = uri.get_host();
		j["port"] = uri.get_port();
		auto username = uri.get_username(); if (!username.empty())j["username"] = username;
		auto pwd = uri.get_password(); if (!pwd.empty())j["password"] = pwd;
		auto path = uri.get_path(); if (!path.empty())j["path"] = path;
		auto frg = uri.get_fragment(); if (!frg.empty())j["fragment"] = frg;
		auto query = uri.get_query_dictionary();
		std::for_each(query.begin(), query.end(), [&](std::pair<std::string, std::string> entry) {
			try {
				// try to parse
				auto num = nlohmann::ordered_json::parse(entry.second);
				j[entry.first] = num;
			}
			catch (std::exception&) {
				// store as string
				j[entry.first] = entry.second;
			}
		});
		return j;
	}

	/**
	* 根据指定的 schema/host/port/username/password 创建 uri 实例
	* @param schema schema to be connected to
	* @param host host to be connected to
	* @param port port to be connected to
	* @param username user name for redis authorization
	* @param password password for redis authorization
	* @param timeout_ms
	* @param max_reconnects
	* @param reconnect_interval_ms
	* @return uri instance
	*/
	inline uri make_uri(const std::string &schema,const std::string &host, size_t port,
		const std::string &username = "",
		const std::string &password = "",
		std::uint32_t timeout_ms = std::numeric_limits<std::uint32_t>::max(),
		std::int32_t max_reconnects = std::numeric_limits<std::uint32_t>::max(),
		std::uint32_t reconnect_interval_ms = std::numeric_limits<std::uint32_t>::max()) {
		std::stringstream ss;
		if (username.empty() && password.empty()) {
			ss << schema << "://" << host << ":" << port;
		}
		else {
			ss << schema << "://" << username << ":" << password << "@" << host << ":" << port;
		}
		if (timeout_ms || max_reconnects || reconnect_interval_ms)
		{
			ss << "?";
			bool ap = false;
			if (timeout_ms != std::numeric_limits<std::uint32_t>::max())
			{
				ss << "timeout_ms=" << timeout_ms;
				ap = true;
			}
			if (max_reconnects != std::numeric_limits<std::uint32_t>::max())
			{
				if (ap)
				{
					ss << "&";
				}
				ss << "max_reconnects=" << max_reconnects;
				ap = true;
			}
			if (reconnect_interval_ms != std::numeric_limits<std::uint32_t>::max())
			{
				if (ap)
				{
					ss << "&";
				}
				ss << "reconnect_interval_ms=" << reconnect_interval_ms;
			}
		}
		return ss.str();
	}

	struct base_mq_client_params : public std::enable_shared_from_this<base_mq_client_params>
	{
		std::string schema;
		int defaultPort;
		std::string host;
		int port;
		std::string password;
		std::string username;
		std::uint32_t timeout_ms;
		std::int32_t max_reconnects;
		std::uint32_t reconnect_interval_ms;
		/**
		 * 根据指定的 schema/host/port/username/password 创建 uri 实例,包含请求参数
		 * @return uri instance
		 */
		virtual uri make_uri() const {
			return simplemq::make_uri(schema,host,port,username,password,timeout_ms,max_reconnects,reconnect_interval_ms);
		}
		/**
		 * 对uri对象进行归一化，输出只包含主机名和端口号的标准的mqtt uri(tcp://host:port)
		 * @param uri
		 * @return uri instance
		 */
		uri host_and_port()const {
			return simplemq::make_uri(schema, host, port);
		}

		nlohmann::ordered_json to_json()const 
		{
			nlohmann::ordered_json j;
			j["host"] = host;
			j["port"] = port;
			j["password"] = password;
			j["username"] = username;
			j["timeout_ms"] = timeout_ms;
			j["max_reconnects"] = max_reconnects;
			j["reconnect_interval_ms"] = reconnect_interval_ms;
			return j;
		}
		_BASIC_JSON_TPL_DECLARATION_
		void from_json(const _BASIC_JSON_TPL_ &params)
		{
			this->host = json_utilits::get_value(params, JN_HOST, DEFAULT_HOST);
			this->port = json_utilits::get_value(params, JN_PORT, defaultPort);
			this->username = json_utilits::get_value(params, JN_USERNAME, DEFAULT_USERNAME);
			this->password = json_utilits::get_value(params, JN_PASSWORD, DEFAULT_PASSWORD);
			this->timeout_ms = json_utilits::get_value(params, JN_TIMEOUT_MS, DEFAULT_TIMEOUT_MS);
			this->max_reconnects = json_utilits::get_value(params, JN_MAX_RECONNECTS, DEFAULT_MAX_RECONNECTS);
			this->reconnect_interval_ms = json_utilits::get_value(params, JN_RECONNECT_INTERVAL_MS, DEFAULT_RECONNECT_INTERVAL_MS);
		}
		std::string to_string() const
		{
			return to_json().dump(4);
		}
	protected:
		/************************************************************************/
		/*  隐藏所有构造函数                                                    */
		/************************************************************************/
		base_mq_client_params(const std::string &schema, int defaultPort,const std::string &host,int port,const std::string &password, const std::string &username,std::uint32_t timeout_ms,std::int32_t max_reconnects,std::uint32_t reconnect_interval_ms)
			:schema(schema),defaultPort(defaultPort), host(host), port(port), password(password), username(username), timeout_ms(timeout_ms), max_reconnects(max_reconnects), reconnect_interval_ms(reconnect_interval_ms){}
		_BASIC_JSON_TPL_DECLARATION_
		base_mq_client_params(const std::string &schema, int defaultPort, const _BASIC_JSON_TPL_ &params)
			: base_mq_client_params(
				schema,
				defaultPort,
				json_utilits::get_value(params, JN_HOST, DEFAULT_HOST),
				json_utilits::get_value(params, JN_PORT, defaultPort),
				json_utilits::get_value(params, JN_USERNAME, DEFAULT_USERNAME),
				json_utilits::get_value(params, JN_PASSWORD, DEFAULT_PASSWORD),
				json_utilits::get_value(params, JN_TIMEOUT_MS, DEFAULT_TIMEOUT_MS),
				json_utilits::get_value(params, JN_MAX_RECONNECTS, DEFAULT_MAX_RECONNECTS),
				json_utilits::get_value(params, JN_RECONNECT_INTERVAL_MS, DEFAULT_RECONNECT_INTERVAL_MS)) {}
		base_mq_client_params(const std::string &schema, int defaultPort, const uri&uri) :base_mq_client_params(schema,defaultPort,parse_components(uri)) {}

	};
	using base_client_params_ptr = std::shared_ptr<base_mq_client_params>;
} /* namespace simplemq */
#endif /* SIMPLEMQ_SIMPLEMQ_COMMONS_HPP_ */
